AIFunctions
AIFunctions are single, focused operations that the agent can call. They are the simplest capability type and the building block for more complex capabilities.
Basic Usage
Mark a method with [AIFunction] to expose it to the agent:
public class CalculatorTool
{
[AIFunction]
public int Add(int a, int b)
{
return a + b;
}
}The agent sees this as a tool called Add that takes two integers and returns an integer.
Descriptions
Function Description
Use [AIDescription] to tell the agent what the function does:
[AIFunction]
[AIDescription("Add two numbers together and return the sum")]
public int Add(int a, int b)
{
return a + b;
}Parameter Descriptions
Apply [AIDescription] to parameters for clarity:
[AIFunction]
[AIDescription("Search the web for information")]
public async Task<string> WebSearch(
[AIDescription("The search query")] string query,
[AIDescription("Maximum number of results (1-10)")] int maxResults = 5)
{
// Implementation
}Return Types
AIFunctions support various return types:
// Synchronous
[AIFunction]
public string GetName() => "HPD-Agent";
// Async
[AIFunction]
public async Task<string> GetNameAsync() => await Task.FromResult("HPD-Agent");
// Complex types (serialized to JSON)
[AIFunction]
public async Task<WeatherResult> GetWeather(string city)
{
return new WeatherResult { Temperature = 72, Condition = "Sunny" };
}
// Void (returns confirmation message)
[AIFunction]
public void LogMessage(string message)
{
Console.WriteLine(message);
}Requiring Permission
Some functions need user approval before execution. Use [RequiresPermission]:
[AIFunction]
[AIDescription("Delete a file from the filesystem")]
[RequiresPermission]
public void DeleteFile(string path)
{
File.Delete(path);
}When the agent calls this function, the user will be prompted to approve or deny the action.
Custom Function Names
Override the default method name:
[AIFunction(Name = "search_web")]
public async Task<string> PerformWebSearch(string query)
{
// Implementation
}The agent sees this as search_web, not PerformWebSearch.
The Collapse Attribute
When a toolkit has multiple functions, you can group them under a collapsible container using [Collapse("description")]:
[Collapse("File operations for reading, writing, and managing files")]
public class FileSystemToolkit
{
[AIFunction]
public string ReadFile(string path) { /* ... */ }
[AIFunction]
public void WriteFile(string path, string content) { /* ... */ }
[AIFunction]
public void DeleteFile(string path) { /* ... */ }
}Note: Providing a description to [Collapse] enables collapsing. Without [Collapse], the toolkit's functions are always visible.
Collapse with Instructions
The [Collapse] attribute supports two instruction types that serve different purposes:
[Collapse(
"Database operations",
FunctionResult: "Available: Query, Insert, Update, Delete. Always use transactions for multiple operations.",
SystemPrompt: "CRITICAL: Never execute DELETE without a WHERE clause."
)]
public class DatabaseToolkit
{
// Functions...
}| Property | Where It Goes | When | Persistence |
|---|---|---|---|
FunctionResult | Tool call result | Once on expansion | In message history |
SystemPrompt | System instructions | Every turn while expanded | Configurable |
FunctionResult: One-time message returned when the container expands. Use for listing available functions or one-time guidance.
SystemPrompt: Injected into system instructions while functions are expanded. Use for critical rules that must be followed every turn.
Dynamic Instructions with Expressions
Reference methods or properties for dynamic instructions:
[Collapse(
"Search operations",
FunctionResult: nameof(GetAvailableSearchers),
SystemPrompt: nameof(SearchGuidelines)
)]
public class SearchToolkit
{
public static string GetAvailableSearchers() =>
$"Available: {string.Join(", ", _enabledSearchers)}";
public static string SearchGuidelines =>
"Always prefer exact matches over fuzzy matches.";
// Functions...
}→ See 02.1.5 Context Engineering.md for advanced collapsing configuration.
Dependency Injection
Tools can receive services through constructor injection:
public class WeatherTool
{
private readonly IWeatherService _weatherService;
private readonly ILogger<WeatherTool> _logger;
public WeatherTool(IWeatherService weatherService, ILogger<WeatherTool> logger)
{
_weatherService = weatherService;
_logger = logger;
}
[AIFunction]
[AIDescription("Get current weather for a city")]
public async Task<WeatherResult> GetWeather(string city)
{
_logger.LogInformation("Getting weather for {City}", city);
return await _weatherService.GetCurrentWeatherAsync(city);
}
}Register the service provider with the agent:
var services = new ServiceCollection()
.AddSingleton<IWeatherService, WeatherService>()
.AddLogging()
.BuildServiceProvider();
var agent = await new AgentBuilder()
.WithServiceProvider(services)
.WithToolkit<WeatherTool>()
.BuildAsync();Generic Attribute Form: [AIFunction<TMetadata>]
The plain [AIFunction] attribute is sufficient for most functions. Use the generic form [AIFunction<TMetadata>] when you need compile-time validation of dynamic descriptions or conditional expressions against a specific metadata type.
// Plain form — no metadata type checking
[AIFunction]
[AIDescription("Search the web")]
public async Task<string> Search(string query) { ... }
// Generic form — descriptions and conditionals are validated against SearchMetadata at compile time
[AIFunction<SearchMetadata>]
[AIDescription("Search using {metadata.ProviderName}")]
public async Task<string> Search(string query) { ... }[AIFunction<TMetadata>] is a separate attribute class with these properties:
public sealed class AIFunctionAttribute<TMetadata> : Attribute
where TMetadata : IToolMetadata
{
public string? Name { get; set; } // Custom name (uses method name if null)
public ToolKind Kind { get; set; } // Function or Output (default: Function)
}The TMetadata type parameter tells the source generator which metadata class to validate expressions against. Dynamic description tokens like {metadata.ProviderName} and conditional expressions like "HasAdvancedFeatures" must resolve to valid members of TMetadata.
→ See 02.1.4 Tool Metadata.md for details on metadata and dynamic descriptions.
ToolKind — Structured Output
ToolKind controls how the framework treats the function when the agent calls it:
public enum ToolKind
{
Function = 0, // Normal tool — executed, result returned to the LLM
Output = 1 // Output tool — calling this ends the agent run; arguments ARE the result
}ToolKind.Output for structured responses
Use ToolKind.Output when you want the agent to produce a typed, validated response instead of free-form text. The agent "calls" the output tool with its arguments — the framework captures those arguments as the structured result and never executes the method. The agent run terminates at that point.
public record WeatherReport(string City, double TempCelsius, string Condition);
public class WeatherToolkit
{
[AIFunction(Kind = ToolKind.Output)]
public WeatherReport Report(
[AIDescription("City name")] string city,
[AIDescription("Temperature in Celsius")] double tempCelsius,
[AIDescription("Weather condition (e.g., 'sunny', 'cloudy')")] string condition)
{
// This method body is never executed.
// The agent's arguments become the structured result.
throw new NotImplementedException();
}
}Retrieve the result with RunStructuredAsync<T>():
var result = await agent.RunStructuredAsync<WeatherReport>(messages, branch);
// result is a WeatherReport with values the agent filled inToolKind.Output is compatible with both [AIFunction] and [AIFunction<TMetadata>].
Generic Attribute Forms for All Tool Types
The pattern applies to all three tool attribute types. Each has a plain form and a generic form:
| Plain | Generic | When to use the generic form |
|---|---|---|
[AIFunction] | [AIFunction<TMetadata>] | Dynamic descriptions or conditionals validated at compile time |
[Skill] | [Skill<TMetadata>] | Skill visibility conditionals tied to a specific metadata type |
[SubAgent] | [SubAgent<TMetadata>] | SubAgent visibility conditionals tied to a specific metadata type |
All three generic attributes follow the same rules:
TMetadatamust implementIToolMetadata- Expressions in
[ConditionalFunction(...)]are validated againstTMetadatamembers - Dynamic description tokens (
{metadata.X}) are validated againstTMetadatamembers
public class MyToolMetadata : IToolMetadata
{
public bool FeatureEnabled { get; set; }
public string ProviderName { get; set; } = "Default";
// ...
}
public class MyToolkit
{
// Generic AIFunction — conditional validated against MyToolMetadata
[AIFunction<MyToolMetadata>]
[ConditionalFunction("FeatureEnabled")]
[AIDescription("Only visible when FeatureEnabled is true")]
public string FeatureFunction() => "...";
// Generic Skill — skill visibility depends on metadata
[Skill<MyToolMetadata>]
[ConditionalFunction("FeatureEnabled")]
public Skill FeatureWorkflow() => SkillFactory.Create(...);
// Generic SubAgent — sub-agent visibility depends on metadata
[SubAgent<MyToolMetadata>]
[ConditionalFunction("FeatureEnabled")]
public SubAgent FeatureAgent() => SubAgentFactory.Create(...);
}→ See 02.1.4 Tool Metadata.md for how to define metadata types and register them with the agent.
Conditional Functions
Show or hide functions based on runtime conditions:
[AIFunction]
[ConditionalFunction("HasAdvancedFeatures")]
[AIDescription("Advanced search with filters")]
public async Task<string> AdvancedSearch(string query, SearchFilters filters)
{
// Only visible when metadata.HasAdvancedFeatures is true
}→ See 02.1.4 Tool Metadata.md for conditional registration details.
Best Practices
Keep functions focused: One function should do one thing well.
Write clear descriptions: The agent relies on descriptions to choose the right function.
Use meaningful parameter names:
cityis better thancorinput.Handle errors gracefully: Return error information rather than throwing exceptions when possible.
Use
[RequiresPermission]for destructive or sensitive operations.Group related functions: Use
[Collapse("description")]to reduce context clutter.
// Good: Focused, well-described, safe
[AIFunction]
[AIDescription("Search for files matching a pattern in a directory")]
public async Task<string[]> FindFiles(
[AIDescription("Directory to search in")] string directory,
[AIDescription("Glob pattern (e.g., '*.txt')")] string pattern)
{
if (!Directory.Exists(directory))
return Array.Empty<string>();
return Directory.GetFiles(directory, pattern);
}
// Bad: Vague, no descriptions, unsafe
[AIFunction]
public void Process(string x)
{
File.Delete(x); // Destructive without permission!
}Next Steps
- 02.1.2 Skills.md - Group functions into workflows
- 02.1.4 Tool Metadata.md - Dynamic descriptions and conditionals
- 02.1.5 Context Engineering.md - Advanced collapsing